﻿using System;
using System.Device.Location;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using Microsoft.Phone.Controls.Maps;
using Ping.App;
using Ping.App.Jobs;
using Ping.Core;

namespace Ping.Client.Pages
{
	public partial class MapResultPage
	{
		private readonly MapLayer _lineLayer;
		private readonly MapLayer _pushPinLayer;

		private Job _currentJob;
		private GeoResult _geoResult;
		private TracerouteResult _traceResult;

		public MapResultPage()
		{
			InitializeComponent();

			_lineLayer = new MapLayer();
			map.Children.Add(_lineLayer);

			_pushPinLayer = new MapLayer();
			map.Children.Add(_pushPinLayer);

			Loaded += PageLoaded;
		}

		private void PageLoaded(object sender, RoutedEventArgs e)
		{
			Clear();

			var result = StateService.State.TraceResult;
			if (result is TracerouteResult)
			{
				_traceResult = (TracerouteResult) result;
				var request = new GeoRequest { Addresses = (from r in _traceResult.Hops select r.Sender).ToArray() };
				_currentJob = StateService.State.Scheduler.GetJob(request);
				_currentJob.Perform(request, RequestFinished);
			}

			UpdatePage();
		}

		public void Clear()
		{
			StateService.State.GeoResult = null;

			_lineLayer.Children.Clear();
			_pushPinLayer.Children.Clear();

			_traceResult = null;
			_geoResult = null;

			_currentJob = null;
		}

		private void RequestFinished(Result result, Exception e)
		{
			Dispatcher.BeginInvoke(() =>
			                       	{
			                       		_currentJob = null;

			                       		StateService.State.GeoResult = result;
			                       		if (result is GeoResult)
			                       		{
			                       			_geoResult = (GeoResult) result;

			                       			var line = new MapPolyline
			                       			           	{
			                       			           		Locations = new LocationCollection(),
			                       			           		Stroke = new SolidColorBrush(Colors.Red),
			                       			           		StrokeThickness = 5,
			                       			           	};

			                       			for (int i = 0; i < _geoResult.Count; ++i)
			                       			{
			                       				PingResult hop = _traceResult.Hops[i];
			                       				GeoDataSet geo = _geoResult.DataSets[i];

			                       				if (hop.Sender == null || geo == null)
			                       					continue;

			                       				line.Locations.Add(geo.Position.AsGeoCoordinate);
			                       				AddPushpin(i, hop, geo);
			                       			}

			                       			_lineLayer.Children.Add(line);
			                       		}

										UpdatePage();
			                       	});
		}


		/// <summary>
		/// Adds a pushpin that represents the given geo-result on the map.
		/// </summary>
		/// <param name="index">The index of the result in the entire trace</param>
		/// <param name="hop">The result of the ping operation</param>
		/// <param name="dataSet">The geo location of the server involed in the operation</param>
		private void AddPushpin(int index, PingResult hop, GeoDataSet dataSet)
		{
			var source = new BitmapImage(new Uri("/Images/Pushpin.png", UriKind.Relative));
			var pushpin = new Image
			              	{
			              		Source = source,
			              		Stretch = Stretch.None,
			              	};

			pushpin.MouseLeftButtonUp += (s, e) => PingClicked(index, hop, dataSet);

			_pushPinLayer.AddChild(pushpin, dataSet.Position.AsGeoCoordinate);

			// The pole of the flag should be centered on the actual geo-location:
			// Therefore an offset is required...
			var offset = new Point {X = -18, Y = -source.PixelHeight};
			MapLayer.SetPositionOffset(pushpin, offset);
		}

		private void PingClicked(int index, PingResult result, GeoDataSet dataSet)
		{
			var trace = (TracerouteResult) StateService.State.TraceResult;

			bool isStart = trace.IndexOf(result) == 0;
			bool isEnd = trace.IndexOf(result) == trace.Count - 1;

			var builder = new StringBuilder();
			builder.AppendFormat("Host: {0} {1}\n", result.Sender, isStart ? "(Source)" : isEnd ? "(Destination)" : string.Empty);
			builder.AppendFormat("RTT: {0} ms\n", (int) result.Rtt.TotalMilliseconds);
			builder.AppendFormat("Position: {0}\n", Format(dataSet.Position));

			if (dataSet.ContinentName != null)
				builder.AppendFormat("Continent: {0}\n", dataSet.ContinentName);
			if (dataSet.CountryName != null)
				builder.AppendFormat("Country: {0}\n", dataSet.CountryName);
			if (dataSet.City != null)
				builder.AppendFormat("City: {0}\n", dataSet.City);

			string message = builder.ToString();
			string title = string.Format("Hop [{0}]", index + 1);
			MessageBox.Show(message, title, MessageBoxButton.OK);
		}

		private static string Format(GeoPosition pos)
		{
			return string.Format("{0}°, {1}°", pos.Latitude, pos.Longitude);
		}

		private void UpdatePage()
		{
			if (_currentJob == null)
			{
				loading.Visibility = Visibility.Collapsed;

				int valid = _traceResult.ValidResultCount;
				int count = _traceResult.Count;

				if ((from f in _traceResult.Hops where f == null select f).Count() == _traceResult.Count)
				{
					description.Text = "Unable to geo locate any IP-Address of the traceroute";
				}
				else if (valid == count)
				{
					description.Text = string.Format("Displaying {0} hops", count);
				}
				else
				{
					description.Text = string.Format("Displaying {0} of {1} hops", valid, count);
				}

				explaination.Visibility = _traceResult.IsPartial ? Visibility.Visible : Visibility.Collapsed;
			}
			else
			{
				loading.Visibility = Visibility.Visible;
				description.Text = "Performing Geo Location...";
				explaination.Visibility = Visibility.Collapsed;
			}
		}

		private void Explaination(object sender, RoutedEventArgs e)
		{
			MessageBox.Show(
				"Some servers do not respond to an ICMP ECHO request and therefore their IP-Address cannot be traced (nor visualized). In addition it's not always possible to geolocate every IP-Address.");
		}
	}
}